// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/renderer/gpu/queue_message_swap_promise.h"

#include <stddef.h>

#include <vector>

#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/memory/scoped_vector.h"
#include "cc/output/swap_promise.h"
#include "content/renderer/gpu/frame_swap_message_queue.h"
#include "content/renderer/gpu/render_widget_compositor.h"
#include "content/renderer/render_widget.h"
#include "content/test/mock_render_process.h"
#include "ipc/ipc_message.h"
#include "ipc/ipc_sync_message_filter.h"
#include "ipc/ipc_test_sink.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace content {

class TestRenderWidget : public RenderWidget {
public:
    using RenderWidget::QueueMessageImpl;

private:
    ~TestRenderWidget() override { }

    DISALLOW_COPY_AND_ASSIGN(TestRenderWidget);
};

class TestSyncMessageFilter : public IPC::SyncMessageFilter {
public:
    TestSyncMessageFilter()
        : IPC::SyncMessageFilter(nullptr)
    {
    }

    bool Send(IPC::Message* message) override
    {
        messages_.push_back(base::WrapUnique(message));
        return true;
    }

    std::vector<std::unique_ptr<IPC::Message>>& messages() { return messages_; }

private:
    ~TestSyncMessageFilter() override { }

    std::vector<std::unique_ptr<IPC::Message>> messages_;

    DISALLOW_COPY_AND_ASSIGN(TestSyncMessageFilter);
};

struct QueueMessageData {
    MessageDeliveryPolicy policy;
    int source_frame_number;
};

class QueueMessageSwapPromiseTest : public testing::Test {
public:
    QueueMessageSwapPromiseTest()
        : frame_swap_message_queue_(new FrameSwapMessageQueue())
        , sync_message_filter_(new TestSyncMessageFilter())
    {
    }

    ~QueueMessageSwapPromiseTest() override { }

    std::unique_ptr<cc::SwapPromise> QueueMessageImpl(
        IPC::Message* msg,
        MessageDeliveryPolicy policy,
        int source_frame_number)
    {
        return TestRenderWidget::QueueMessageImpl(
            msg, policy, frame_swap_message_queue_.get(), sync_message_filter_,
            source_frame_number);
    }

    const std::vector<std::unique_ptr<IPC::Message>>& DirectSendMessages()
    {
        return sync_message_filter_->messages();
    }

    std::vector<std::unique_ptr<IPC::Message>>& NextSwapMessages()
    {
        next_swap_messages_.clear();
        std::unique_ptr<FrameSwapMessageQueue::SendMessageScope>
            send_message_scope = frame_swap_message_queue_->AcquireSendMessageScope();
        frame_swap_message_queue_->DrainMessages(&next_swap_messages_);
        return next_swap_messages_;
    }

    bool ContainsMessage(
        const std::vector<std::unique_ptr<IPC::Message>>& messages,
        const IPC::Message& message)
    {
        if (messages.empty())
            return false;
        for (const auto& msg : messages) {
            if (msg->type() == message.type())
                return true;
        }
        return false;
    }

    bool NextSwapHasMessage(const IPC::Message& message)
    {
        return ContainsMessage(NextSwapMessages(), message);
    }

    void QueueMessages(QueueMessageData data[], size_t count)
    {
        for (size_t i = 0; i < count; ++i) {
            messages_.push_back(
                IPC::Message(0, i + 1, IPC::Message::PRIORITY_NORMAL));
            promises_.push_back(
                QueueMessageImpl(new IPC::Message(messages_[i]),
                    data[i].policy,
                    data[i].source_frame_number)
                    .release());
        }
    }

    void CleanupPromises()
    {
        for (ScopedVector<cc::SwapPromise>::iterator i = promises_.begin();
             i != promises_.end();
             ++i) {
            if (*i) {
                (*i)->DidActivate();
                (*i)->WillSwap(NULL);
                (*i)->DidSwap();
            }
        }
    }

protected:
    void VisualStateSwapPromiseDidNotSwap(
        cc::SwapPromise::DidNotSwapReason reason);

    base::MessageLoop message_loop_;
    scoped_refptr<FrameSwapMessageQueue> frame_swap_message_queue_;
    scoped_refptr<TestSyncMessageFilter> sync_message_filter_;
    std::vector<IPC::Message> messages_;
    ScopedVector<cc::SwapPromise> promises_;

private:
    std::vector<std::unique_ptr<IPC::Message>> next_swap_messages_;

    DISALLOW_COPY_AND_ASSIGN(QueueMessageSwapPromiseTest);
};

TEST_F(QueueMessageSwapPromiseTest, NextSwapPolicySchedulesMessageForNextSwap)
{
    QueueMessageData data[] = {
        /* { policy, source_frame_number } */
        { MESSAGE_DELIVERY_POLICY_WITH_NEXT_SWAP, 1 },
    };
    QueueMessages(data, arraysize(data));

    ASSERT_TRUE(promises_[0]);
    promises_[0]->DidActivate();
    promises_[0]->WillSwap(NULL);
    promises_[0]->DidSwap();

    EXPECT_TRUE(DirectSendMessages().empty());
    EXPECT_FALSE(frame_swap_message_queue_->Empty());
    // frame_swap_message_queue_->WillSwap(1);
    EXPECT_TRUE(NextSwapHasMessage(messages_[0]));
}

TEST_F(QueueMessageSwapPromiseTest, NextSwapPolicyNeedsAtMostOnePromise)
{
    QueueMessageData data[] = {
        /* { policy, source_frame_number } */
        { MESSAGE_DELIVERY_POLICY_WITH_NEXT_SWAP, 1 },
        { MESSAGE_DELIVERY_POLICY_WITH_NEXT_SWAP, 1 },
    };
    QueueMessages(data, arraysize(data));

    ASSERT_TRUE(promises_[0]);
    ASSERT_FALSE(promises_[1]);

    CleanupPromises();
}

TEST_F(QueueMessageSwapPromiseTest, NextSwapPolicySendsMessageOnNoUpdate)
{
    QueueMessageData data[] = {
        /* { policy, source_frame_number } */
        { MESSAGE_DELIVERY_POLICY_WITH_NEXT_SWAP, 1 },
    };
    QueueMessages(data, arraysize(data));

    promises_[0]->DidNotSwap(cc::SwapPromise::COMMIT_NO_UPDATE);
    EXPECT_TRUE(ContainsMessage(DirectSendMessages(), messages_[0]));
    EXPECT_TRUE(NextSwapMessages().empty());
    EXPECT_TRUE(frame_swap_message_queue_->Empty());
}

TEST_F(QueueMessageSwapPromiseTest, NextSwapPolicySendsMessageOnSwapFails)
{
    QueueMessageData data[] = {
        /* { policy, source_frame_number } */
        { MESSAGE_DELIVERY_POLICY_WITH_NEXT_SWAP, 1 },
    };
    QueueMessages(data, arraysize(data));

    promises_[0]->DidNotSwap(cc::SwapPromise::SWAP_FAILS);
    EXPECT_TRUE(ContainsMessage(DirectSendMessages(), messages_[0]));
    EXPECT_TRUE(NextSwapMessages().empty());
    EXPECT_TRUE(frame_swap_message_queue_->Empty());
}

TEST_F(QueueMessageSwapPromiseTest, NextSwapPolicyRetainsMessageOnCommitFails)
{
    QueueMessageData data[] = {
        /* { policy, source_frame_number } */
        { MESSAGE_DELIVERY_POLICY_WITH_NEXT_SWAP, 1 },
    };
    QueueMessages(data, arraysize(data));

    promises_[0]->DidNotSwap(cc::SwapPromise::COMMIT_FAILS);
    EXPECT_TRUE(DirectSendMessages().empty());
    EXPECT_FALSE(frame_swap_message_queue_->Empty());
    frame_swap_message_queue_->DidSwap(2);
    EXPECT_TRUE(NextSwapHasMessage(messages_[0]));
}

TEST_F(QueueMessageSwapPromiseTest,
    VisualStateQueuesMessageWhenCommitRequested)
{
    QueueMessageData data[] = {
        /* { policy, source_frame_number } */
        { MESSAGE_DELIVERY_POLICY_WITH_VISUAL_STATE, 1 },
    };
    QueueMessages(data, arraysize(data));

    ASSERT_TRUE(promises_[0]);
    EXPECT_TRUE(DirectSendMessages().empty());
    EXPECT_FALSE(frame_swap_message_queue_->Empty());
    EXPECT_TRUE(NextSwapMessages().empty());

    CleanupPromises();
}

TEST_F(QueueMessageSwapPromiseTest,
    VisualStateQueuesMessageWhenOtherMessageAlreadyQueued)
{
    QueueMessageData data[] = {
        /* { policy, source_frame_number } */
        { MESSAGE_DELIVERY_POLICY_WITH_VISUAL_STATE, 1 },
        { MESSAGE_DELIVERY_POLICY_WITH_VISUAL_STATE, 1 },
    };
    QueueMessages(data, arraysize(data));

    EXPECT_TRUE(DirectSendMessages().empty());
    EXPECT_FALSE(frame_swap_message_queue_->Empty());
    EXPECT_FALSE(NextSwapHasMessage(messages_[1]));

    CleanupPromises();
}

TEST_F(QueueMessageSwapPromiseTest, VisualStateSwapPromiseDidActivate)
{
    QueueMessageData data[] = {
        /* { policy, source_frame_number } */
        { MESSAGE_DELIVERY_POLICY_WITH_VISUAL_STATE, 1 },
        { MESSAGE_DELIVERY_POLICY_WITH_VISUAL_STATE, 1 },
        { MESSAGE_DELIVERY_POLICY_WITH_VISUAL_STATE, 2 },
    };
    QueueMessages(data, arraysize(data));

    promises_[0]->DidActivate();
    promises_[0]->WillSwap(NULL);
    promises_[0]->DidSwap();
    ASSERT_FALSE(promises_[1]);
    std::vector<std::unique_ptr<IPC::Message>> messages;
    messages.swap(NextSwapMessages());
    EXPECT_EQ(2u, messages.size());
    EXPECT_TRUE(ContainsMessage(messages, messages_[0]));
    EXPECT_TRUE(ContainsMessage(messages, messages_[1]));
    EXPECT_FALSE(ContainsMessage(messages, messages_[2]));

    promises_[2]->DidActivate();
    promises_[2]->DidNotSwap(cc::SwapPromise::SWAP_FAILS);
    messages.swap(NextSwapMessages());
    EXPECT_TRUE(messages.empty());

    EXPECT_EQ(1u, DirectSendMessages().size());
    EXPECT_TRUE(ContainsMessage(DirectSendMessages(), messages_[2]));

    EXPECT_TRUE(NextSwapMessages().empty());
    EXPECT_TRUE(frame_swap_message_queue_->Empty());
}

void QueueMessageSwapPromiseTest::VisualStateSwapPromiseDidNotSwap(
    cc::SwapPromise::DidNotSwapReason reason)
{
    QueueMessageData data[] = {
        /* { policy, source_frame_number } */
        { MESSAGE_DELIVERY_POLICY_WITH_VISUAL_STATE, 1 },
        { MESSAGE_DELIVERY_POLICY_WITH_VISUAL_STATE, 1 },
        { MESSAGE_DELIVERY_POLICY_WITH_VISUAL_STATE, 2 },
    };
    QueueMessages(data, arraysize(data));

    // If we fail to swap with COMMIT_FAILS or ACTIVATE_FAILS, then
    // messages are delivered by the RenderFrameHostImpl destructor,
    // rather than directly by the swap promise.
    bool msg_delivered = reason != cc::SwapPromise::COMMIT_FAILS && reason != cc::SwapPromise::ACTIVATION_FAILS;

    promises_[0]->DidNotSwap(reason);
    ASSERT_FALSE(promises_[1]);
    EXPECT_TRUE(NextSwapMessages().empty());
    EXPECT_EQ(msg_delivered, ContainsMessage(DirectSendMessages(), messages_[0]));
    EXPECT_EQ(msg_delivered, ContainsMessage(DirectSendMessages(), messages_[1]));
    EXPECT_FALSE(ContainsMessage(DirectSendMessages(), messages_[2]));

    promises_[2]->DidNotSwap(reason);
    EXPECT_TRUE(NextSwapMessages().empty());
    EXPECT_EQ(msg_delivered, ContainsMessage(DirectSendMessages(), messages_[2]));

    EXPECT_TRUE(NextSwapMessages().empty());
    EXPECT_EQ(msg_delivered, frame_swap_message_queue_->Empty());
}

TEST_F(QueueMessageSwapPromiseTest, VisualStateSwapPromiseDidNotSwapNoUpdate)
{
    VisualStateSwapPromiseDidNotSwap(cc::SwapPromise::COMMIT_NO_UPDATE);
}

TEST_F(QueueMessageSwapPromiseTest,
    VisualStateSwapPromiseDidNotSwapCommitFails)
{
    VisualStateSwapPromiseDidNotSwap(cc::SwapPromise::COMMIT_FAILS);
}

TEST_F(QueueMessageSwapPromiseTest, VisualStateSwapPromiseDidNotSwapSwapFails)
{
    VisualStateSwapPromiseDidNotSwap(cc::SwapPromise::SWAP_FAILS);
}

TEST_F(QueueMessageSwapPromiseTest,
    VisualStateSwapPromiseDidNotSwapActivationFails)
{
    VisualStateSwapPromiseDidNotSwap(cc::SwapPromise::ACTIVATION_FAILS);
}

} // namespace content
